package com.biboheart.adapter.core.impl;

import com.biboheart.adapter.compiler.Compiler;
import com.biboheart.adapter.compiler.NormalCompiler;
import com.biboheart.adapter.core.Composition;
import com.biboheart.adapter.model.AdapterConfigModel;
import com.biboheart.adapter.model.StructureNode;
import com.biboheart.adapter.support.enums.ConverterTypeEnum;
import com.biboheart.adapter.support.enums.InstructEnum;
import com.biboheart.adapter.support.enums.ValueTypeEnum;
import com.biboheart.adapter.support.utils.ObjectTypeUtils;
import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.lang.Math.abs;

public class NormalComposition implements Composition {
    private final AdapterConfigModel adapterConfigModel;
    private final StructureNode structureTree;
    private final Compiler compiler = new NormalCompiler();
    private final Map<String, StructureNode> hash = new HashMap<>();

    public NormalComposition(AdapterConfigModel adapterConfigModel) {
        this.adapterConfigModel = adapterConfigModel;
        this.structureTree = new StructureNode();
        this.analyseStructure();
    }

    private void analyseStructure() {
        this.structureTree.setNumber("0");
        this.structureTree.setLevel(0);
        this.structureTree.setPath("root");
        this.structureTree.setTargetObjectName(null);
        this.structureTree.setTargetName("root");
        this.structureTree.setSourcePath("root");
        this.structureTree.setSourceObjectName(null);
        this.structureTree.setSourceName("root");
        this.structureTree.setChildren(new ArrayList<>());
        if (null == this.adapterConfigModel) {
            return;
        }
        push(this.adapterConfigModel, null);
    }

    @Override
    public StructureNode getNode(String path) {
        return hash.get(path);
    }

    private void push(AdapterConfigModel adapterConfigModel, StructureNode parent) {
        if (null == adapterConfigModel) {
            return;
        }
        ValueTypeEnum type = ValueTypeEnum.getValueType(adapterConfigModel.getTargetClass());
        if (null == type) {
            type = ValueTypeEnum.VALUE_TYPE_STRING;
        }
        String sourceObject = adapterConfigModel.getSourceObject();
        String sourceName = adapterConfigModel.getSourceName();
        String targetName = adapterConfigModel.getTargetName();
        StructureNode node;
        if (null == parent) {
            node = this.structureTree;
            node.setValueType(type);
            if (null != sourceName && !"".equals(sourceName)) {
                node.setSourcePath("root." + sourceName);
                node.setSourceObjectName(null);
                node.setSourceName(sourceName);
            }
        } else {
            if (StringUtils.isEmpty(targetName)) {
                return;
            }
            if (StringUtils.isEmpty(sourceObject)) {
                sourceObject = parent.getSourcePath();
            }
            String objectPath = parent.getPath();
            node = new StructureNode();
            node.setNumber(parent.getNumber() + (parent.getChildren().size() + 1));
            node.setLevel(parent.getLevel() + 1);
            node.setPath(objectPath + "." + targetName);
            node.setTargetObjectName(objectPath);
            node.setTargetName(targetName);
            node.setValueType(type);
            node.setChildren(new ArrayList<>());
            parent.getChildren().add(node);
            node.setSourceObjectName(sourceObject);
            if (null != sourceName && !"".equals(sourceName)) {
                node.setSourcePath(sourceObject + "." + sourceName);
                node.setSourceName(sourceName);
            } else {
                node.setSourcePath(null);
                node.setSourceName(null);
            }
        }
        if (null != adapterConfigModel.getExpression()) {
            node.setExpression(adapterConfigModel.getExpression());
        } else {
            node.setExpression(generateExpression(adapterConfigModel, node));
        }
        if (!StringUtils.isEmpty(node.getExpression())) {
            node.setAst(compiler.build(node.getExpression()));
        }
        hash.put(node.getPath(), node);
        if (null == node.getSourceName()) {
            return;
        }
        if (!type.equals(ValueTypeEnum.VALUE_TYPE_LIST) && !type.equals(ValueTypeEnum.VALUE_TYPE_MAP)) {
            return;
        }
        List<AdapterConfigModel> children = adapterConfigModel.getChildren();
        if (null == children || children.isEmpty()) {
            return;
        }
        for (AdapterConfigModel child : children) {
            push(child, node);
        }
    }

    @SuppressWarnings("unchecked")
    private String generateExpression(AdapterConfigModel adapterConfigModel, StructureNode node) {
        if (null == adapterConfigModel) {
            return null;
        }
        AdapterConfigModel def = adapterConfigModel.getDef();
        ConverterTypeEnum converter = ConverterTypeEnum.getConverterType(adapterConfigModel.getConverter());
        ValueTypeEnum valueType = ValueTypeEnum.getValueType(adapterConfigModel.getTargetClass());
        if (ValueTypeEnum.VALUE_TYPE_LIST.equals(valueType) || ValueTypeEnum.VALUE_TYPE_MAP.equals(valueType)) {
            return null;
        }
        String sourcePath = node.getSourcePath();
        StringBuilder sb = new StringBuilder();
        Object auxiliary = adapterConfigModel.getAuxiliary();
        String auxiliaryStr = null == auxiliary ? null : String.valueOf(auxiliary);
        if (ConverterTypeEnum.CONVERTER_ENUM.equals(converter) && null != auxiliaryStr) {
            Map<String, Object> enumMap = ObjectTypeUtils.convertType(auxiliaryStr, Map.class);
            Set<Map.Entry<String, Object>> entrySet = enumMap.entrySet();
            sb.append(InstructEnum.INSTRUCT_WHEN.code()).append(" ").append(sourcePath);
            for (Map.Entry<String, Object> entry : entrySet) {
                if ("def".equals(entry.getKey())) {
                    sb.append(" ").append(InstructEnum.INSTRUCT_ELSE.code()).append(" ").append(entry.getValue());
                }
                sb.append(" ").append(InstructEnum.INSTRUCT_WHEN.code()).append(" ").append(entry.getKey()).append(" ").append(InstructEnum.INSTRUCT_THEN.code()).append(" ").append(entry.getValue());
            }
            sb.append(" ").append(InstructEnum.INSTRUCT_END.code());
        } else if (ConverterTypeEnum.CONVERTER_BINARY_DIGIT.equals(converter) && null != auxiliary) {
            int digit = ObjectTypeUtils.convertType(auxiliary, Integer.class);
            if (digit < 1) {
                sb.append(sourcePath);
            } else {
                sb.append(InstructEnum.INSTRUCT_BINARY.code()).append("(").append(sourcePath).append(",").append(digit - 1).append(")");
            }
        } else if (ConverterTypeEnum.CONVERTER_BINARY_DIGIT_IN.equals(converter) && null != auxiliary) {
            int digit = ObjectTypeUtils.convertType(auxiliary, Integer.class);
            if (digit < 1) {
                sb.append(sourcePath);
            } else {
                sb.append(InstructEnum.INSTRUCT_BINARY.code()).append("(").append(sourcePath).append(",").append(digit - 1).append(",1").append(")");
            }
        } else if (ConverterTypeEnum.CONVERTER_REPLACE.equals(converter) && null != auxiliaryStr) {
            auxiliaryStr = auxiliaryStr.replaceAll("\\{\\[value]}", "{}");
            sb.append(InstructEnum.INSTRUCT_REPLACE.code()).append("(").append(auxiliaryStr).append(",").append(sourcePath).append(")");
        } else if (ConverterTypeEnum.CONVERTER_FILL.equals(converter) && null != auxiliaryStr) {
            Set<String> transitions = new HashSet<>();
            Pattern pattern = Pattern.compile("(?=\\{\\[)(.+?)(]})");
            Matcher matcher = pattern.matcher(auxiliaryStr);
            while (matcher.find()) {
                transitions.add(matcher.group());
            }
            if (!transitions.isEmpty()) {
                Set<String> pathSet = new HashSet<>();
                for (String transition : transitions) {
                    auxiliaryStr = auxiliaryStr.replace(transition, "{}");
                    String[] transitionArr = transition.replace("{[", "").replace("]}", "").split("\\|");
                    String key = transitionArr[0].trim();
                    pathSet.add(node.getSourceObjectName() + "." + key);
                }
                sb.append(InstructEnum.INSTRUCT_REPLACE.code()).append("(").append(auxiliaryStr);
                for (String str : pathSet) {
                    sb.append(",").append(str);
                }
                sb.append(")");
            }
        } else if (ConverterTypeEnum.CONVERTER_SPLIT.equals(converter) && null != auxiliaryStr) {
            sb.append(InstructEnum.INSTRUCT_SPLIT.code()).append("(").append(sourcePath).append(",").append(auxiliaryStr).append(")");
        } else if (ConverterTypeEnum.CONVERTER_RIGHT_RADIX_POINT.equals(converter) && null != auxiliaryStr) {
            Map<String, Object> params = ObjectTypeUtils.convertType(auxiliary, Map.class);
            Integer moveLen = ObjectTypeUtils.convertType(params.get("move"), Integer.class);
            Integer fractional = ObjectTypeUtils.convertType(params.get("fractional"), Integer.class);
            sb.append(InstructEnum.INSTRUCT_ROUND.code()).append("(").append(sourcePath).append("*").append(BigDecimal.valueOf(Math.pow(10, abs(moveLen)))).append(",").append(fractional).append(")");
        } else if (ConverterTypeEnum.CONVERTER_LEFT_RADIX_POINT.equals(converter) && null != auxiliaryStr) {
            Map<String, Object> params = ObjectTypeUtils.convertType(auxiliary, Map.class);
            Integer moveLen = ObjectTypeUtils.convertType(params.get("move"), Integer.class);
            Integer fractional = ObjectTypeUtils.convertType(params.get("fractional"), Integer.class);
            sb.append(InstructEnum.INSTRUCT_ROUND.code()).append("(").append(sourcePath).append("/").append(BigDecimal.valueOf(Math.pow(10, abs(moveLen)))).append(",").append(fractional).append(")");
        } else if (ConverterTypeEnum.CONVERTER_TIME_MILLIS.equals(converter)) {
            sb.append(InstructEnum.INSTRUCT_TIME.code()).append("(").append(sourcePath);
            if (null != auxiliaryStr) {
                sb.append(",").append(auxiliaryStr);
            }
            sb.append(")");
        } else if (ConverterTypeEnum.CONVERTER_MILLIS_TIME.equals(converter)) {
            sb.append(InstructEnum.INSTRUCT_TIME.code()).append("(").append(sourcePath);
            if (null != auxiliaryStr) {
                sb.append(",").append(auxiliaryStr);
            }
            sb.append(")");
        } else if (ConverterTypeEnum.CONVERTER_BINARY_MILLIS.equals(converter)) {
            sb.append(InstructEnum.INSTRUCT_TIME.code()).append("(");
            if (null != auxiliaryStr) {
                sb.append(",").append(auxiliaryStr);
            }
            sb.append(")");
        } else {
            sb.append(sourcePath);
        }
        if (null != def) {
            sb.insert(0, "(").insert(0, InstructEnum.INSTRUCT_DEF.code()).append(", ").append(def.getAuxiliary()).append(")");
        }
        if (null != valueType) {
            sb.insert(0, "(").insert(0, InstructEnum.INSTRUCT_CONVERT.code()).append(", '").append(valueType.code()).append("')");
        }
        return sb.toString();
    }
}
